/*
CSE30332 Final Project - Iris Matcher
Group Oatmeal

Comparison Program:
===================
This program performs two methods of comparison of unwrapped iris images. 
The two methods of comparison are the hamming distance of two input byte 
codes and the euclidean distance of two images using floating point pixel 
values as the values compared.  The hamming distance performs the hamming 
distance 13 times (shifting the code 1 byte each time) to account for 
rotational variance in the iris images and then outputs the highest match 
score as the match score for the pair. It also accounts for invalid bits 
(bits that are above or below the designated pixel intensity in the 
filter.c program).  The output is a floating point value between 0 and 1 
where higher values indicate closer matches.  The euclidean distance is 
computed from two CSV files output from the filter.c program and performs
the euclidean distance between the two files 13 times (again, accounting 
for rotation) and outputs the 0-1 match score where higher is a closer
match.

We have determined experimentally that the hamming distance works better,
and gives us a lower FAR and FRR for the experimental data of our 60 images.
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

int main(int argc, char* argv[]) 
{
	char debug = 0;
	char * infile1 = "";
	char * infile2 = "";
	int hamming = 0;
	int euc = 0;
	int help = 0;

	// usage statement
	if (argc <= 1)
	{
		fprintf(stderr,"Usage: %s -i INFILE1 -f INFILE2 -[dxe] \n",argv[0]);
		fprintf(stderr,"-e or -x must be specified to get an output score\n"
		"If -x is specified, the hamming distance will be computed and output\n"
		"If -e is specified, the euclidean distance will be computed and" 
		"(by default, output is shown onscreen but not written to a file)\n"
		"If -d is specified, enable debug output\n"
		"-h displays this message\n");
		exit(-1);
	}

	// processing command line opts 
	while(1) 
	{
		int c = getopt(argc,argv,"do:i:f:xe");
		if (c == -1) break;
		switch(c) 
		{
			case 'd': debug = 1; break;
			case 'x': hamming = 1; break;
			case 'i': infile1 = optarg; break;
			case 'f': infile2 = optarg; break;
			case 'e': euc = 1; break;
			case 'h': help = 1; break;
			case '?': fprintf(stderr,"FAIL: malformed option list\n");
		}
	}
	
	//display usage if -h option is set
	if (help)
	{
		fprintf(stderr,"Usage: %s -i INFILE1 -f INFILE2 -[dxe] \n",argv[0]);
		fprintf(stderr,"-e or -x must be specified to get an output score\n"
		"If -x is specified, the hamming distance will be computed and output\n"
		"If -e is specified, the euclidean distance will be computed and" 
		"(by default, output is shown onscreen but not written to a file)\n"
		"If -d is specified, enable debug output\n"
		"-h displays this message\n");
		exit(-1);
	}
	
	//simple debug info
	if (debug)
	{
		printf("Debugging\n");
		printf("Infile1: %s\n",infile1);
		printf("Infile2: %s\n",infile2);
	}
	
	//perform the hamming comparison
	if (hamming)
	{
		FILE * f1 = fopen(infile1,"r");
		FILE * f2 = fopen(infile2,"r");
		
		if (f1 == NULL || f2 == NULL)
		{
			fprintf(stderr,"FAIL: file not found\n");
			exit(-1);
		}
		
		char c1, c2;
		float total = 0.0;
		float fscore = 0.0;
		
        //we ignore the bottom 4 rows of the image because of the pupil
		int height = 36;
		float maxscore = 0.0;
		
		//start the loop at -12 to account for -12 degrees rotation
		int x = -12;
		int zero_count,one_count,X_count;
		char same = 0;
		int same_count = 0, max_same_count = 0;
		float score = 0.0;

		for (x; x < 13; x++)
		{
			//reset all the variables for the next hamming calculation
			total = 0.0;
			score = 0;
			c1 = 0;
			c2 = 0;
			X_count = 0;
			zero_count = 0;
			one_count = 0;
			fscore = 0.0;
			same = 0;
			same_count = 0;
			max_same_count = 0;

			if (debug) printf("X: %d\n",x);
			
			//we perform the shifting of the byte codes by changing where
			//the program starts reading the file from.  if x is negative 
			//then we start near the end of the file and start comparing,
			//resetting the file pointer when we reach the end of the file.
			//one file pointer stays the same, and we end the comparison
			//when that file pointer reads EOF
			
			//move f1 to where it should be to account for rotation
			if (x < 0)
			{
				fseek(f1,x*height,SEEK_END);
			}
			else if (x > 0)
			{
				fseek(f1,x*height,SEEK_SET);
			}
			else if (x == 0)
			{
				fseek(f1,0,SEEK_SET);
			}
			
			//move f2 to the beginning, it is the static byte code
			fseek(f2,0,SEEK_SET);
			
			//loop over the byte codes
			while (c2 != EOF)
			{
		  		c1 = fgetc(f1);
				c2 = fgetc(f2);
				
				//reset the file pointer if f1 reads EOF
				if (c1 == EOF)
				{
					fseek(f1,0,SEEK_SET);
					c1 = fgetc(f1);
				}
				
				
				//most of this loop is for debugging, except where noted
				if (c2 != EOF)
				{
					if (c1 == '1')
						one_count++;
					else if (c1 == '0')
						zero_count++;
					else if (c1 == 'X')
						X_count++;
				
					
					if (c2 == '1')
						one_count++;
					else if (c2 == '0')
						zero_count++;
					else if (c2 == 'X')
						X_count++;
				
					if (c1 == c2)
					{
						same = 1;
						if (same)
							same_count++;
					}				
					else
					{
						same_count = 0;
						same = 0;
					}
					
					if (same_count > max_same_count)
						max_same_count = same_count;
					
					//this is where the comparison is done
					//if either of the characters isn't valid
					//we do not compare them.				
					if  (c1 != 'X' || c2 != 'X')
					{
						total++;
						score += (c1-c2 != 0);						
					}
				}
			
			}
			
			if (debug) printf("Total: %f, Score: %d\n",total,score);
			
			//turn score into a matching score between 0 and 1
			fscore = (float)(1-score/total);
			if (debug) printf("Fscore: %f\n",fscore);
			
			//keep track of the maximum score
			if (fscore > maxscore)
				maxscore = fscore;
				
			if (debug) printf("Same: %d\n",max_same_count);
				
			if (debug) printf("maxscore: %f\n",maxscore);

		}

		if (debug) printf("X: %d, 0: %d, 1: %d\n",X_count,zero_count,one_count);
		
		//output the max score to the script as a single floating point number
	    printf("%f",maxscore);
				
		fclose(f1);
		fclose(f2);
	}
	
	//perform the euclidean comparison
	if (euc)
	{
		char * s;
		char * s2;
		char * buffer;
		char * buffer2;
		FILE * f1 = fopen(infile1,"r");
		FILE * f2 = fopen(infile2,"r");
		
		if (f1 == NULL || f2 == NULL)
		{
			fprintf(stderr,"FAIL: file not found\n");
			exit(-1);
		}
		
		//create two buffers to read each line into
		buffer = (char*) malloc (20);
		buffer2 = (char*) malloc (20);
		
		if (buffer == NULL || buffer2 == NULL)
		{
			printf("Not enough memory.\n");
		}
		
		
		float i1, i2;
		float sum = 0.0, total=0.0;
		int height = 36;
		float maxscore = 0.0;
		int i = -12;
		float sq;
		float match, max_match = 0.0;
		float to;
		int x;
		int count = 0;
		char c1;
		
		//this loop counts the lines in the file
		while (1)
		{
			c1 = fgetc(f1);
			if (c1 == EOF)
				break;
			else if (c1 == '\n')
				count++;
		}
		
		//loop over the files, shifting each time for rotation
		for (i; i < 13; i++)
		{
			//reset the variables
			sum = 0.0;
			total = 0.0;
			to = 0.0;
			sq = 0.0;
			memset(buffer, 0, 20);
			memset(buffer2, 0, 20);
			match = 0;
			
			//shifting the file pointer this time was a little bit more
			//difficult because of the variable length of the floating
			//point pixel values and the use of newlines to delimit
			//them.  basically what we did was to read each line a 
			//certain number of times to place the file pointer at the
			//beginning of the line where we wanted to begin.  we 
			//couldn't just move the file pointer because then it could
			//end up in the middle of a number.  in these next if
			//statements, we position the file pointer where it needs
			//to be based on the height of each column in the image
			//and the number of lines in the file			
			if (i < 0)
			{	
				fseek(f1,0,SEEK_SET);
				for (x = 0; x < (count + i*height);x++)
				{
					fgets(buffer, 20, f1);
				}
			}
			else if (i > 0)
			{
				fseek(f1,0,SEEK_SET);
				for (x = 0; x < i*height;x++)
				{
					fgets(buffer, 20, f1);
				}
			}
			else if (i == 0)
			{
				fseek(f1,0,SEEK_SET);
			}
			
			fseek(f2,0,SEEK_SET);
			
			memset(buffer, 0, 20);
			memset(buffer2, 0, 20);
			
			//loop over the files, using f2 as the base of when to end
			// comparison, similar to above in the hamming function
			while (!feof(f2))
			{
				i1 = 0;
				i2 = 0;
				s = fgets(buffer, 20, f1);
				
				//if we reach the end of the file, reset the file pointer
				if(feof(f1))
				{
					fseek(f1,0,SEEK_SET);
					*buffer = 0;
					s = fgets(buffer,20,f1);
				}
			
				s2 = fgets(buffer2, 20, f2);
				
				//convert the two numbers to floating point
				i1 = atof(buffer);
				i2 = atof(buffer2);
				
				//if either of them are 0, then it is invalid (that
				//is how filter.c outputs invalid bits) and we do not
				//compare the numbers, otherwise we subtract them and 
				//square the result, incrementing sum with that number
				if (!feof(f2) && (i1 != 0.0 || i2 != 0.0))
				{
					sum += (i1-i2) * (i1-i2);
					total++;
				}
			} 
			
			//we then divide the square root of the sum by the total 
			//number of comparisons and then invert it to make it so
			//1 is a good match and 0 is a non match.
			sq = sqrt(sum);
			sq /= total;
			match = 1 - (sq);
			
			//store the maximum of these values
			if (match > max_match)
				max_match = match;
		}
		
		//print the result to stdout for use by the script
		printf("%f", max_match);
		
		//free memory and close the open files
		free(buffer);
		free(buffer2);
		fclose(f1);
		fclose(f2);
	}
	
	return 0;
}
